package xyz.app;

import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.metamodel.SingularAttribute;
import lombok.NonNull;
import xyz.app.accessors.DbCreator;
import xyz.app.accessors.DbDeleter;
import xyz.app.accessors.DbReader;
import xyz.app.accessors.DbUpdater;
import xyz.app.AbstractDomain_;

import java.util.*;
import java.util.stream.Collectors;

public interface Dao<T extends AbstractDomain> extends
        DbCreator<T>, DbReader<T>, DbUpdater<T>, DbDeleter<T> {

    @NonNull Class<T> domainClass();

    @NonNull DaoRegistry daoRegistry();

    default @NonNull EntityManagerFactory getEmf() {
        return daoRegistry().getEmf();
    }

    private Optional<Class<? extends AbstractDomain_>> metamodelClass() {
        var metamodelClassName = domainClass().getName() + "_";

        try {
            var metamodel = Class.forName(metamodelClassName);

            info("Metamodel class for extracting attributes: [{}]", metamodel.getSimpleName());
            return AbstractDomain_.class.isAssignableFrom(metamodel) ?
                    Optional.of(metamodel.asSubclass(AbstractDomain_.class)) : Optional.empty();
        }
        catch (ClassNotFoundException e) {
            error("Failed to find class for name [{}]", metamodelClassName);
            e.printStackTrace();
            return Optional.empty();
        }
    }

    @SuppressWarnings("unchecked")
    default @NonNull Collection<SingularAttribute<? super T, ?>> extractAttributes() {
        return metamodelClass()
                .map(metamodel -> {

                    info("Attempt to extract attributes [{}]", metamodel.getSimpleName());

                    return Arrays.stream(metamodel.getFields())
                            .filter(field -> SingularAttribute.class.isAssignableFrom(field.getType()))
                            .map(field -> {
                                try {
                                    return field.get(null);
                                }
                                catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                    return Set.of();
                                }
                            })
                            .filter(Objects::nonNull)
                            .map(attribute -> (SingularAttribute<? super T, ?>) attribute)
                            .collect(Collectors.<SingularAttribute<? super T, ?>>toSet());
                })
                .orElseGet(Set::of);
    }
}